package com.nl.web.handler;

import com.nl.model.consts.HttpStatusEnum;
import com.nl.model.exception.BizProcessException;
import com.nl.model.exception.ParamCheckException;
import com.nl.model.exception.SystemException;
import com.nl.model.exception.UnAuthorizedException;
import com.nl.model.model.ResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import org.springframework.validation.BindException;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 全局异常处理.
 *
 * @author breggor
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 缺少必要的请求参数异常.
     *
     * @param ex MissingServletRequestParameterException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseVO handleException(final MissingServletRequestParameterException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(String.format("缺少必要的请求参数: %s", ex.getParameterName()));
    }

    /**
     * 请求参数格式错误异常.
     *
     * @param ex MethodArgumentTypeMismatchException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseVO handleException(final MethodArgumentTypeMismatchException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(String.format("请求参数格式错误: %s", ex.getName()));
    }

    /**
     * 用户未登陆异常.
     *
     * @param ex UnAuthorizedException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(UnAuthorizedException.class)
    public ResponseVO handleException(final UnAuthorizedException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(ex.getMessage());
    }

    /**
     * 请求参数验证失败异常.
     *
     * @param ex MethodArgumentNotValidException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseVO handleException(final MethodArgumentNotValidException ex) {
        log.error(ex.getMessage(), ex);
        return buildFieldErrors(ex.getBindingResult().getFieldErrors());
    }

    /**
     * 请求参数绑定异常.
     *
     * @param ex BindException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public ResponseVO handleException(final BindException ex) {
        log.error(ex.getMessage(), ex);
        return buildFieldErrors(ex.getBindingResult().getFieldErrors());
    }

    /**
     * 没有找到资源异常.
     *
     * @param ex NoHandlerFoundException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseVO handleException(final NoHandlerFoundException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(HttpStatusEnum.NOT_FOUND.getCode(), ex.getMessage());
    }

    /**
     * 请求没有可读数据异常.
     *
     * @param ex HttpMessageNotReadableException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseVO handleException(final HttpMessageNotReadableException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(HttpStatusEnum.BAD_REQUEST.getCode(), "消息不能读取");
    }

    /**
     * 后端业务检查异常.
     *
     * @param ex ParamCheckException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ParamCheckException.class)
    public ResponseVO handleException(final ParamCheckException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(HttpStatusEnum.BAD_REQUEST.getCode(), ex.getMessage());
    }

    /**
     * 请求方法不被服务支持异常拦截.
     *
     * @param ex HttpRequestMethodNotSupportedException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseVO handleException(final HttpRequestMethodNotSupportedException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(HttpStatusEnum.METHOD_NOT_ALLOWED.getCode(), ex.getMessage());
    }

    /**
     * 服务器拒绝服务，原因是请求格式不被支持异常拦截.
     *
     * @param ex HttpMediaTypeNotSupportedException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ResponseVO handleException(final HttpMediaTypeNotSupportedException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(HttpStatusEnum.UNSUPPORTED_MEDIA_TYPE.getCode(), ex.getMessage());
    }

    /**
     * NPE异常.
     *
     * @param ex NullPointerException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(NullPointerException.class)
    public ResponseVO handleException(final NullPointerException ex) {
        log.error(ex.getMessage(), ex);
        ex.printStackTrace();
        return ResponseVO.fail(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), "NPE-异常");
    }

    /**
     * 后端业务异常.
     *
     * @param ex BizProcessException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BizProcessException.class)
    public ResponseVO handleException(final BizProcessException ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(String.valueOf(ex.getCode()), ex.getMessage());
    }

    /**
     * RuntimeException 处理.
     *
     * @param ex RuntimeException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(RuntimeException.class)
    public ResponseVO handleRuntimeException(final RuntimeException ex) {
        log.error(ex.getMessage(), ex);
        if (ex instanceof ParamCheckException) {
            return ResponseVO.fail(String.valueOf(((ParamCheckException) ex).getCode()), ex.getMessage());
        } else if (ex instanceof BizProcessException) {
            return ResponseVO.fail(String.valueOf(((BizProcessException) ex).getCode()), ex.getMessage());
        } else if (ex instanceof SystemException) {
            return ResponseVO.fail(String.valueOf(((SystemException) ex).getCode()), ex.getMessage());
        } else {
            //RPC异常不抛出异常信息
            if ("org.apache.dubbo.rpc.RpcException".equals(ex.getClass().getName())) {
                return ResponseVO.fail(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), "服务端异常");
            }
            return ResponseVO.fail(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), ex.getMessage());
        }
    }

    /**
     * 错误处理.
     *
     * @param ex RuntimeException
     * @return ResponseVO
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({Throwable.class, Error.class})
    public ResponseVO handleError(final Throwable ex) {
        log.error(ex.getMessage(), ex);
        return ResponseVO.fail(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), ex.getMessage());
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({DataAccessException.class})
    public ResponseVO dataException(final DataAccessException dataAccessException) {
        log.error(dataAccessException.getMessage(), dataAccessException);
        return ResponseVO.fail(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), "系统数据异常");
    }

    /**
     * 根据字段异常构建.
     *
     * @param fieldErrors fieldErrors
     * @return ResponseVO
     */
    private ResponseVO buildFieldErrors(final List<FieldError> fieldErrors) {
        String error = fieldErrors.stream()
            .map(x -> x.getField() + ":" + x.getDefaultMessage())
            .collect(Collectors.joining(";"));
        return ResponseVO.fail(error);
    }
}
